﻿/*
 * LINQ to LDAP
 * http://linqtoldap.codeplex.com/
 * 
 * Copyright Alan Hatter (C) 2010-2012
 * alan.hatter@gmail.com - http://hattercoding.blogspot.com/
 * 
 * This project is subject to licensing restrictions. Visit http://linqtoldap.codeplex.com/license for more information.
 */

using System;
using System.DirectoryServices.Protocols;
using System.Linq;
using System.Reflection;
using LinqToLdap.Exceptions;
using LinqToLdap.Logging;
using LinqToLdap.Mapping;

namespace LinqToLdap
{
    /// <summary>
    /// Class for configuring LINQ to LDAP.
    /// </summary>
    public class LdapConfiguration : ILdapConfiguration
    {
        private int _serverMaxPageSize = 500;
        private bool _pagingEnabled = true;

        /// <summary>
        /// Constructs an instance of this class and initializes <see cref="Mapper"/>.
        /// </summary>
        public LdapConfiguration()
        {
            Mapper = new DirectoryMapper();
        }

        /// <summary>
        /// Deconstructor that sets related properties to null.
        /// </summary>
        ~LdapConfiguration()
        {
            ConnectionFactory = null;
            Log = null;
            Mapper = null;
        }
        
        /// <summary>
        /// The configured connection factory to be used for all 
        /// <see cref="DirectoryContext"/>s that don't explicitly get an <see cref="LdapConnection"/>.
        /// </summary>
        public ILdapConnectionFactory ConnectionFactory { get; private set; }

        /// <summary>
        /// Used for writing <see cref="DirectoryRequest"/> and <see cref="DirectoryResponse"/> information to a log.
        /// </summary>
        public ILinqToLdapLogger Log { get; private set; }

        /// <summary>
        /// Class responsible for mapping objects to directory entries.
        /// </summary>
        public IDirectoryMapper Mapper { get; set; }

        /// <summary>
        /// Static storage for a global configuration.
        /// </summary>
        public static ILdapConfiguration Configuration { get; private set; }

        /// <summary>
        /// Get the server max page size.  Default is 500.  Change this value with <see cref="MaxPageSizeIs"/>.
        /// </summary>
        public int ServerMaxPageSize { get { return _serverMaxPageSize; } }

        /// <summary>
        /// Indicates if paging is enabled.
        /// </summary>
        public bool PagingEnabled
        {
            get { return _pagingEnabled; }
        }

        /// <summary>
        /// Indicates the maximum number of results the server can return at a time.
        /// </summary>
        /// <param name="size">The maximum amount.</param>
        /// <returns></returns>
        public LdapConfiguration MaxPageSizeIs(int size)
        {
            _serverMaxPageSize = size;
            return this;
        }

        /// <summary>
        /// Disables paging for all <see cref="DirectoryContext"/>s using this configuration.
        /// </summary>
        /// <returns></returns>
        public LdapConfiguration DisablePaging()
        {
            _pagingEnabled = false;
            return this;
        }
        
        /// <summary>
        /// Adds the mapping for querying.
        /// </summary>
        /// <param name="classMap">The mapping for the class</param>
        /// <returns></returns>
        public LdapConfiguration AddMapping(IClassMap classMap)
        {
            Mapper.Map(classMap);
            
            return this;
        }

        /// <summary>
        /// Adds all mappings in the assembly.
        /// </summary>
        /// <param name="assembly">
        /// The assembly containing all of the mappings
        /// </param>
        /// <returns></returns>
        public LdapConfiguration AddMappingsFrom(Assembly assembly)
        {
            if (assembly == null) throw new ArgumentNullException("assembly");

            foreach (var type in assembly.GetTypes().Where(t => !t.IsInterface))
            {
                if (type.HasDirectorySchema())
                {
                    var classMapType = typeof (AttributeClassMap<>).MakeGenericType(type);
                    AddMapping(Activator.CreateInstance(classMapType) as IClassMap);
                }
                else
                {
                    var baseType = type.BaseType;
                    while (baseType != typeof(object))
                    {
                        if (baseType.IsGenericType &&
                            baseType.GetGenericTypeDefinition() == typeof(ClassMap<>))
                        {
                            AddMapping(Activator.CreateInstance(type) as IClassMap);
                            break;
                        }
                        baseType = baseType.BaseType;
                    }
                }
            }

            return this;
        }

        /// <summary>
        /// Adds all mappings in the assembly.
        /// </summary>
        /// <param name="assemblyName">
        /// The assembly containing all of the mappings
        /// </param>
        /// <returns></returns>
        public LdapConfiguration AddMappingsFrom(string assemblyName)
        {
            if (assemblyName.IsNullOrEmpty()) 
                throw new ArgumentNullException("assemblyName");

            assemblyName = assemblyName.ToLower().EndsWith(".dll")
                                ? assemblyName
                                : assemblyName + ".dll";

            var assembly = Assembly.LoadFrom(assemblyName);

            return AddMappingsFrom(assembly);
        }

        /// <summary>
        /// Starts configuration of <see cref="LdapConnectionFactory"/>.
        /// </summary>
        /// <param name="serverName">
        /// The server name can be an IP address, a DNS domain or host name.
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// THrown if <paramref name="serverName"/> is null, empty or white-space.
        /// </exception>
        public ILdapConnectionFactoryConfiguration ConfigureFactory(string serverName)
        {
            if (ConnectionFactory != null)
                throw new MappingException("A connection factory has already been configured.");

            var factory = new LdapConnectionFactory(serverName) { Logger = Log };

            ConnectionFactory = factory;

            return factory;
        }

        /// <summary>
        /// Starts configuration of a connection factory that uses connection pooling.
        /// </summary>
        /// <param name="serverName">
        /// The server name can be an IP address, a DNS domain or host name.
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// THrown if <paramref name="serverName"/> is null, empty or white-space.
        /// </exception>
        public IPooledConnectionFactoryConfiguration ConfigurePooledFactory(string serverName)
        {
            if (ConnectionFactory != null)
                throw new MappingException("A connection factory has already been configured.");

            var factory = new PooledLdapConnectionFactory(serverName){Logger = Log};

            ConnectionFactory = factory;

            return factory;
        }

        /// <summary>
        /// Configure a custom connection factory.
        /// </summary>
        /// <param name="customFactory">The factory.</param>
        /// <typeparam name="T">Type of the factory</typeparam>
        /// <returns></returns>
        public T ConfigureCustomFactory<T>(T customFactory) where T : ILdapConnectionFactory
        {
            if (ConnectionFactory != null) 
                throw new MappingException("A connection factory has already been configured.");

            ConnectionFactory = customFactory;
            

            return customFactory;
        }

        /// <summary>
        /// Sets <see cref="Configuration"/> for global configuration access.
        /// </summary>
        /// <returns></returns>
        /// <exception cref="MappingException">Throw if this method is called more than once.</exception>
        public LdapConfiguration UseStaticStorage()
        {
            if (Configuration != null)
            {
                throw new MappingException("Static storage has already been set.");
            }
            Configuration = this;

            return this;
        }

        /// <summary>
        /// Creates a <see cref="DirectoryContext"/> from the configuration.
        /// </summary>
        ///<returns></returns>
        public IDirectoryContext CreateContext()
        {
            return new DirectoryContext(this);
        }

        /// <summary>
        /// Sets the logger for <see cref="ConnectionFactory"/> and <see cref="DirectoryContext"/>s created or configured by this class.
        /// </summary>
        /// <param name="log">The log implementation.</param>
        /// <returns></returns>
        public LdapConfiguration LogTo(ILinqToLdapLogger log)
        {
            Log = log;
            
            if (ConnectionFactory is ConnectionFactoryBase)
            {
                (ConnectionFactory as ConnectionFactoryBase).Logger = Log;
            }

            return this;
        }
    }
}
